﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework;

namespace FerrSimplicity
{
    public enum MouseKey
    {
        Left,
        Middle,
        Right
    }
    public enum KeyEvent
    {
        Down,
        JustDown,
        Up,
        JustUp
    }

    public class SystemInput : IUpdateable
    {
        #region Singleton
        private static SystemInput mInstance = null;
        public  static SystemInput Instance
        {
            get { if (mInstance == null) mInstance = new SystemInput(); return mInstance; }
        }
        private SystemInput() 
        {
            Enabled = true;
        }
        #endregion

        #region Fields
        KeyboardState keyState;
        KeyboardState prevKeyState;

        List<Keys>     keysEaten    = new List<Keys>    ();
        List<Buttons>  buttonsEaten = new List<Buttons> ();
        List<MouseKey> clicksEaten  = new List<MouseKey>();

        MouseState mouseState;
        MouseState prevMouseState;

        int mouseX;
        int mouseY;
        int prevMouseX;
        int prevMouseY;

        public bool LockMouse = false;

        GamePadState[] padState     = new GamePadState[4];
        GamePadState[] prevPadState = new GamePadState[4];
        public float analogThreshold = 0.5f;

        Vector2[] lStick = new Vector2[4];
        Vector2[] rStick = new Vector2[4];

        public bool Enabled { get; set; }

        public Ray MouseRay;
        #endregion

        #region Properties
        public int PadsConnected
        {
            get
            {
                int gamePadCount = 0;
                for (gamePadCount = 0; gamePadCount < 4; gamePadCount++)
                {
                    if (!GamePad.GetCapabilities((PlayerIndex)gamePadCount).IsConnected)
                        break;
                }
                return gamePadCount;
            }
        }

        public int MouseX
        {
            get { return mouseX; }
        }
        public int MouseY
        {
            get { return mouseY; }
        }
        public int MouseDX
        {
            get { return mouseX - prevMouseX; }
        }
        public int MouseDY
        {
            get { return mouseY - prevMouseY; }
        }

        public Vector2 MousePos
        {
            get { return new Vector2(mouseX, mouseY); }
        }
        public Vector2 MouseChange
        {
            get { return new Vector2(mouseX - prevMouseX, mouseY - prevMouseY); }
        }

        public int ScrollValue
        {
            get { return mouseState.ScrollWheelValue - prevMouseState.ScrollWheelValue; }
        }
        #endregion

        #region General Input
        public bool KeyDown    (Keys aKey)
        {
            return keyState.IsKeyDown(aKey);
        }
        public bool KeyJustDown(Keys aKey)
        {
            for (int i = 0; i < keysEaten.Count; i++)
                if (keysEaten[i] == aKey)
                    return false;
            return (keyState.IsKeyDown(aKey) && prevKeyState.IsKeyUp(aKey));
        }
        public bool KeyUp      (Keys aKey)
        {
            return keyState.IsKeyUp(aKey);
        }
        public bool KeyJustUp  (Keys aKey)
        {
            for (int i = 0; i < keysEaten.Count; i++)
                if (keysEaten[i] == aKey)
                    return false;
            return (keyState.IsKeyUp(aKey) && prevKeyState.IsKeyDown(aKey));
        }

        public void EatKey     (Keys     aKey)
        {
            keysEaten.Add(aKey);
        }
        public void EatButton  (Buttons  aButtons)
        {
            buttonsEaten.Add(aButtons);
        }
        public void EatClick   (MouseKey aKey)
        {
            clicksEaten.Add(aKey);
        }

        public bool MouseDown    (MouseKey aKey)
        {
            switch (aKey)
            {
                case MouseKey.Left:
                    return mouseState.LeftButton == ButtonState.Pressed;
                case MouseKey.Middle:
                    return mouseState.MiddleButton == ButtonState.Pressed;
                case MouseKey.Right:
                    return mouseState.RightButton == ButtonState.Pressed;
            }
            return false;
        }
        public bool MouseJustDown(MouseKey aKey)
        {
            for (int i = 0; i < clicksEaten.Count; i++)
                if (clicksEaten[i] == aKey)
                    return false;
            switch (aKey)
            {
                case MouseKey.Left:
                    return mouseState.LeftButton == ButtonState.Pressed && prevMouseState.LeftButton == ButtonState.Released;
                case MouseKey.Middle:
                    return mouseState.MiddleButton == ButtonState.Pressed && prevMouseState.MiddleButton == ButtonState.Released;
                case MouseKey.Right:
                    return mouseState.RightButton == ButtonState.Pressed && prevMouseState.RightButton == ButtonState.Released;
            }
            return false;
        }
        public bool MouseUp      (MouseKey aKey)
        {
            switch (aKey)
            {
                case MouseKey.Left:
                    return mouseState.LeftButton == ButtonState.Released;
                case MouseKey.Middle:
                    return mouseState.MiddleButton == ButtonState.Released;
                case MouseKey.Right:
                    return mouseState.RightButton == ButtonState.Released;
            }
            return false;
        }
        public bool MouseJustUp  (MouseKey aKey)
        {
            for (int i = 0; i < clicksEaten.Count; i++)
                if (clicksEaten[i] == aKey)
                    return false;
            switch (aKey)
            {
                case MouseKey.Left:
                    return mouseState.LeftButton == ButtonState.Released && prevMouseState.LeftButton == ButtonState.Pressed;
                case MouseKey.Middle:
                    return mouseState.MiddleButton == ButtonState.Released && prevMouseState.MiddleButton == ButtonState.Pressed;
                case MouseKey.Right:
                    return mouseState.RightButton == ButtonState.Released && prevMouseState.RightButton == ButtonState.Pressed;
            }
            return false;
        }

        public bool PadDown    (int aPlayer, Buttons aButton)
        {
            return padState[aPlayer].IsButtonDown(aButton);
        }
        public bool PadJustDown(int aPlayer, Buttons aButton)
        {
            for (int i = 0; i < buttonsEaten.Count; i++)
                if (buttonsEaten[i] == aButton)
                    return false;
            return padState[aPlayer].IsButtonDown(aButton) && prevPadState[aPlayer].IsButtonUp(aButton);
        }
        public bool PadUp      (int aPlayer, Buttons aButton)
        {
            return padState[aPlayer].IsButtonUp(aButton);
        }
        public bool PadJustUp  (int aPlayer, Buttons aButton)
        {
            for (int i = 0; i < buttonsEaten.Count; i++)
                if (buttonsEaten[i] == aButton)
                    return false;
            return padState[aPlayer].IsButtonUp(aButton) && prevPadState[aPlayer].IsButtonDown(aButton);
        }

        public Vector2 RightStick(int aPlayer)
        {
            return rStick[aPlayer];
        }
        public Vector2 LeftStick (int aPlayer)
        {
            return lStick[aPlayer];
        }
        #endregion

        #region GamePad Input
        public bool LeftAnalogJustLeft  (int aPlayer)
        {
            return padState[aPlayer].ThumbSticks.Left.X < -analogThreshold &&
                   prevPadState[aPlayer].ThumbSticks.Left.X >= -analogThreshold;
        }
        public bool LeftAnalogJustRight (int aPlayer)
        {
            return padState[aPlayer].ThumbSticks.Left.X > analogThreshold &&
                   prevPadState[aPlayer].ThumbSticks.Left.X <= analogThreshold;
        }
        public bool LeftAnalogJustDown  (int aPlayer)
        {
            return padState[aPlayer].ThumbSticks.Left.Y < -analogThreshold &&
                   prevPadState[aPlayer].ThumbSticks.Left.Y >= -analogThreshold;
        }
        public bool LeftAnalogJustUp    (int aPlayer)
        {
            return padState[aPlayer].ThumbSticks.Left.Y > analogThreshold &&
                   prevPadState[aPlayer].ThumbSticks.Left.Y <= analogThreshold;
        }

        public bool LeftAnalogLeft      (int aPlayer)
        {
            return padState[aPlayer].ThumbSticks.Left.X < -analogThreshold;
        }
        public bool LeftAnalogRight     (int aPlayer)
        {
            return padState[aPlayer].ThumbSticks.Left.X > analogThreshold;
        }
        public bool LeftAnalogDown      (int aPlayer)
        {
            return padState[aPlayer].ThumbSticks.Left.Y < -analogThreshold;
        }
        public bool LeftAnalogUp        (int aPlayer)
        {
            return padState[aPlayer].ThumbSticks.Left.Y > analogThreshold;
        }

        public bool RightAnalogJustLeft (int aPlayer)
        {
            return padState[aPlayer].ThumbSticks.Right.X < -analogThreshold &&
                   prevPadState[aPlayer].ThumbSticks.Right.X >= -analogThreshold;
        }
        public bool RightAnalogJustRight(int aPlayer)
        {
            return padState[aPlayer].ThumbSticks.Right.X > analogThreshold &&
                   prevPadState[aPlayer].ThumbSticks.Right.X <= analogThreshold;
        }
        public bool RightAnalogJustDown (int aPlayer)
        {
            return padState[aPlayer].ThumbSticks.Right.Y < -analogThreshold &&
                   prevPadState[aPlayer].ThumbSticks.Right.Y >= -analogThreshold;
        }
        public bool RightAnalogJustUp   (int aPlayer)
        {
            return padState[aPlayer].ThumbSticks.Right.Y > analogThreshold &&
                   prevPadState[aPlayer].ThumbSticks.Right.Y <= analogThreshold;
        }
        #endregion

        #region Mouse
        public void SetMousePosition(int aX, int aY)
        {
            Mouse.SetPosition(aX, aY);
            prevMouseState = mouseState = Mouse.GetState();
        }
        #endregion

        #region IUpdatable
        public void Update(GameTime gameTime)
        {
            if (Enabled == false)
            {
                keyState     = new KeyboardState();
                prevKeyState = keyState;

                mouseState   = new MouseState();

                for (int i = 0; i < 4; i++)
                {
                    prevPadState[i] = new GamePadState();
                    padState    [i] = new GamePadState();

                }
            }
            else
            {
                prevKeyState = keyState;
                keyState = Keyboard.GetState();

                prevMouseState = mouseState;
                if (LockMouse)
                {
                }
                else
                {
                    prevMouseX = mouseX;
                    prevMouseY = mouseY;
                }
                mouseState = Mouse.GetState();
                mouseX     = mouseState.X;
                mouseY     = mouseState.Y;

                for (int i = 0; i < 4; i++)
                {
                    prevPadState[i] = padState[i];
                    padState    [i] = GamePad.GetState((PlayerIndex)i);

                    rStick[i] = padState[i].ThumbSticks.Right;
                    lStick[i] = padState[i].ThumbSticks.Left;
                }

                if (LockMouse)
                {
                    Mouse.SetPosition(prevMouseX, prevMouseY);
                }
            }

            keysEaten   .Clear();
            buttonsEaten.Clear();
            clicksEaten .Clear();
        }

        public event EventHandler<EventArgs> EnabledChanged;
        public event EventHandler<EventArgs> UpdateOrderChanged;

        public int UpdateOrder
        {
            get { return 1; }
        }
        #endregion
    }
}
